package jmine.tec.web.wicket.spring;

import jmine.tec.web.wicket.BeanReference;

import org.apache.wicket.Application;
import org.apache.wicket.protocol.http.WebApplication;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ApplicationContextEvent;
import org.springframework.web.context.support.WebApplicationContextUtils;

/**
 * Implementacao de {@link SpringBeanReference}. Vai buscar o bean apenas no {@link #get()}. Se o parametro {@link Class} for passado, ele
 * vai fazer o cast explicito. Somente deve ser usado com beans 'singleton'.
 * 
 * @author takeshi
 * @param <T> o tipo do parametro
 */
public class SpringBeanReference<T> extends jmine.tec.utils.spring.SpringBeanReference<T> implements BeanReference<T>, ApplicationListener {

    /**
     * C'tor
     * 
     * @param beanName o nome do bean
     * @deprecated usar o factory-method {@link #forName(String)}
     */
    public SpringBeanReference(String beanName) {
        super(beanName, null, null);
        if (!this.getBeanFactory().containsBean(beanName)) {
            throw new IllegalArgumentException("No bean for name: " + beanName + " found");
        }
    }

    /**
     * C'tor
     * 
     * @param beanName String
     * @param expectedType Class
     * @deprecated usar o factory-method {@link #forNameAndType(String, Class)}
     */
    public SpringBeanReference(String beanName, Class<? extends T> expectedType) {
        super(beanName, null, expectedType);
        if (!this.getBeanFactory().containsBean(beanName)) {
            throw new IllegalArgumentException("No bean for name: " + beanName + " found");
        }

    }

    /**
     * C'tor que vai buscar o primeiro objeto cujo tipo bate com o passado. Lanca uma {@link IllegalArgumentException} se nenhum bean nao
     * existir
     * 
     * @param expectedType {@link Class}
     * @deprecated usar o factory-method {@link #forType(Class)}
     */
    public SpringBeanReference(Class<? extends T> expectedType) {
        super(null, null, expectedType);
        String[] namesForType = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.getBeanFactory(), expectedType);
        if (namesForType.length == 0) {
            throw new IllegalArgumentException("No bean for type: " + expectedType + " found");
        }
        if (namesForType.length > 1) {
            throw new IllegalArgumentException("More than 1 bean for type: " + expectedType + " found");
        }
        this.name = namesForType[0];
    }

    /**
     * Construtor interno.
     * 
     * @param name nome do bean
     * @param bean instância do bean
     * @param type classe do bean
     */
    protected SpringBeanReference(String name, T bean, Class<? extends T> type) {
        super(name, bean, type);
    }

    /**
     * Obtêm uma referência do bean através de sua instância.
     * 
     * @param <T> classe do bean
     * @param bean instância do bean
     * @return a referência, ou null caso o bean passado seja nulo
     */
    public static <T> SpringBeanReference<T> forBean(T bean) {
        return bean == null ? null : new SpringBeanReference<T>(null, bean, null);
    }

    /**
     * Devolve um {@link SpringBeanReference} por tipo
     * 
     * @param <E> type
     * @param type {@link Class}
     * @return {@link SpringBeanReference}
     */
    @SuppressWarnings("deprecation")
    public static <E> SpringBeanReference<E> forType(Class<E> type) {
        return new SpringBeanReference<E>(type);
    }

    /**
     * Devolve uma {@link SpringBeanReference} para um beanName
     * 
     * @param <E> o tipo
     * @param beanName String
     * @return {@link SpringBeanReference}
     */
    @SuppressWarnings("deprecation")
    public static <E> SpringBeanReference<E> forName(String beanName) {
        return new SpringBeanReference<E>(beanName);
    }

    /**
     * Devolve um {@link SpringBeanReference} por tipo e nome
     * 
     * @param <E> o tipo
     * @param beanName o nome
     * @param type o tipo
     * @return {@link SpringBeanReference}
     */
    @SuppressWarnings("deprecation")
    public static <E> SpringBeanReference<E> forNameAndType(String beanName, Class<E> type) {
        return new SpringBeanReference<E>(beanName, type);
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
     */
    @Deprecated
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationContextEvent) {
            this.reload();
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    @Override
    public ListableBeanFactory getBeanFactory() {
        SpringBeanLocal local = getSpringBeanLocal();
        if (!local.hasBeanFactory()) {
            local.setBeanFactory(WebApplicationContextUtils.getRequiredWebApplicationContext(((WebApplication) Application.get())
                    .getServletContext()));
        }
        return local.getBeanFactory();
    }
}